﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using Nop.Core;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Plugin.Payments.PayPalExpress.Models;
using Nop.Plugin.Payments.PayPalExpress.PayPalSvc;
using Nop.Plugin.Payments.PayPalExpress;
using Nop.Services.Catalog;
using Nop.Services.Configuration;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Tax;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using System.Net;
using System.IO;
using Nop.Core.Domain.Common;
using Nop.Services.Shipping;

namespace Nop.Plugin.Payments.PayPalExpress.Controllers
{
    public class PaymentPayPalExpressController : BaseNopPaymentController
    {
        private readonly ISettingService _settingService;
        private readonly IPaymentService _paymentService;
        private readonly IPriceFormatter _priceFormatter;
        private readonly IOrderService _orderService;
        private readonly IOrderProcessingService _orderProcessingService;
        private readonly ILogger _logger;
        private readonly PayPalExpressCheckoutPaymentSettings _paypalExpressPaymentSettings;
        private readonly PaymentSettings _paymentSettings;
        private readonly ILocalizationService _localizationService;
        private readonly IWorkContext _workContext;
        private readonly OrderSettings _orderSettings;
        private readonly CurrencySettings _currencySettings;
        private readonly ICurrencyService _currencyService;
        private readonly IOrderTotalCalculationService _orderTotalCalculationService;
        private readonly ICustomerService _customerService;
        private readonly IStateProvinceService _stateProvinceService;
        private readonly IShippingService _shippingService;
        private readonly ICountryService _countryService;

        public PaymentPayPalExpressController(ISettingService settingService, 
            IPaymentService paymentService, IOrderService orderService, 
            IOrderProcessingService orderProcessingService,
            ILogger logger, PayPalExpressCheckoutPaymentSettings paypalExpressPaymentSettings,
            PaymentSettings paymentSettings, ILocalizationService localizationService, IWorkContext workContext, OrderSettings orderSettings,
            CurrencySettings currencySettings, ICurrencyService currencyService, IOrderTotalCalculationService orderTotalCalculationService,
            ICustomerService customerService, IStateProvinceService stateProvinceService, IShippingService shippingService, ICountryService countryService, IPriceFormatter priceFormatter)
        {
            this._settingService = settingService;
            this._paymentService = paymentService;
            this._orderService = orderService;
            this._orderProcessingService = orderProcessingService;
            this._logger = logger;
            this._paypalExpressPaymentSettings = paypalExpressPaymentSettings;
            this._paymentSettings = paymentSettings;
            this._localizationService = localizationService;
            this._workContext = workContext;
            this._orderSettings = orderSettings;
            this._currencySettings = currencySettings;
            this._currencyService = currencyService;
            this._orderTotalCalculationService = orderTotalCalculationService;
            this._customerService = customerService;
            this._shippingService = shippingService;
            this._stateProvinceService = stateProvinceService;
            this._countryService = countryService;
            this._priceFormatter = priceFormatter;
        }

        [AdminAuthorize]
        [ChildActionOnly]
        public ActionResult Configure()
        {
            var model = new ConfigurationModel();
            model.UseSandbox = _paypalExpressPaymentSettings.UseSandbox;
            model.TransactModeId = Convert.ToInt32(_paypalExpressPaymentSettings.TransactMode);
            model.ApiAccountName = _paypalExpressPaymentSettings.ApiAccountName;
            model.ApiAccountPassword = _paypalExpressPaymentSettings.ApiAccountPassword;
            model.Signature = _paypalExpressPaymentSettings.Signature;
            model.AdditionalFee = _paypalExpressPaymentSettings.AdditionalFee;
            model.TransactModeValues = _paypalExpressPaymentSettings.TransactMode.ToSelectList();
            model.DefaultShippingPrice = _paypalExpressPaymentSettings.DefaultShippingPrice;
            model.CallbackTimeout = _paypalExpressPaymentSettings.CallbackTimeout;

            return View("Nop.Plugin.Payments.PayPalExpress.Views.PaymentPayPalExpress.Configure", model);
        }

        [HttpPost]
        [AdminAuthorize]
        [ChildActionOnly]
        public ActionResult Configure(ConfigurationModel model)
        {
            if (!ModelState.IsValid)
                return Configure();

            //save settings
            _paypalExpressPaymentSettings.UseSandbox = model.UseSandbox;
            _paypalExpressPaymentSettings.TransactMode = (TransactMode)model.TransactModeId;
            _paypalExpressPaymentSettings.ApiAccountName = model.ApiAccountName;
            _paypalExpressPaymentSettings.ApiAccountPassword = model.ApiAccountPassword;
            _paypalExpressPaymentSettings.Signature = model.Signature;
            _paypalExpressPaymentSettings.AdditionalFee = model.AdditionalFee;
            _paypalExpressPaymentSettings.DefaultShippingPrice = model.DefaultShippingPrice;
            _paypalExpressPaymentSettings.CallbackTimeout = model.CallbackTimeout;
            _settingService.SaveSetting(_paypalExpressPaymentSettings);

            model.TransactModeValues = _paypalExpressPaymentSettings.TransactMode.ToSelectList();

            return View("Nop.Plugin.Payments.PayPalExpress.Views.PaymentPayPalExpress.Configure", model);
        }

        [ChildActionOnly]
        public ActionResult PaymentInfo()
        {
            var model = new PaymentInfoModel();
            return View("Nop.Plugin.Payments.PayPalExpress.Views.PaymentPayPalExpress.PaymentInfo", model);
        }

        [NonAction]
        public override IList<string> ValidatePaymentForm(FormCollection form)
        {
            throw new NotImplementedException();
            //var warnings = new List<string>();

            ////validate
            //var validator = new PaymentInfoValidator(_localizationService);
            //var model = new PaymentInfoModel();
            //var validationResult = validator.Validate(model);
            //if (!validationResult.IsValid)
            //    foreach (var error in validationResult.Errors)
            //        warnings.Add(error.ErrorMessage);
            //return warnings;
        }

        [NonAction]
        public override ProcessPaymentRequest GetPaymentInfo(FormCollection form)
        {
            var paymentInfo = new ProcessPaymentRequest();
            return paymentInfo;
        }

        [ValidateInput(false)]
        public ActionResult IPNHandler()
        {
            byte[] param = Request.BinaryRead(Request.ContentLength);
            string strRequest = Encoding.ASCII.GetString(param);
            Dictionary<string, string> values;

            var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress") as PayPalExpressCheckoutPaymentProcessor;
            if (processor == null ||
                !processor.IsPaymentMethodActive(_paymentSettings) || !processor.PluginDescriptor.Installed)
                throw new NopException("PayPal Express module cannot be loaded");

            if (processor.VerifyIPN(strRequest, out values))
            {
                #region values
                decimal total = decimal.Zero;
                try
                {
                    total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US"));
                }
                catch { }

                string payer_status = string.Empty;
                values.TryGetValue("payer_status", out payer_status);
                string payment_status = string.Empty;
                values.TryGetValue("payment_status", out payment_status);
                string pending_reason = string.Empty;
                values.TryGetValue("pending_reason", out pending_reason);
                string mc_currency = string.Empty;
                values.TryGetValue("mc_currency", out mc_currency);
                string txn_id = string.Empty;
                values.TryGetValue("txn_id", out txn_id);
                string txn_type = string.Empty;
                values.TryGetValue("txn_type", out txn_type);
                string rp_invoice_id = string.Empty;
                values.TryGetValue("rp_invoice_id", out rp_invoice_id);
                string payment_type = string.Empty;
                values.TryGetValue("payment_type", out payment_type);
                string payer_id = string.Empty;
                values.TryGetValue("payer_id", out payer_id);
                string receiver_id = string.Empty;
                values.TryGetValue("receiver_id", out receiver_id);
                string invoice = string.Empty;
                values.TryGetValue("invoice", out invoice);
                string payment_fee = string.Empty;
                values.TryGetValue("payment_fee", out payment_fee);

                #endregion

                var sb = new StringBuilder();
                sb.AppendLine("Paypal IPN:");
                foreach (KeyValuePair<string, string> kvp in values)
                {
                    sb.AppendLine(kvp.Key + ": " + kvp.Value);
                }

                var newPaymentStatus = PaypalHelper.GetPaymentStatus(payment_status, pending_reason);
                sb.AppendLine("New payment status: " + newPaymentStatus);

                switch (txn_type)
                {
                    case "recurring_payment_profile_created":
                        //do nothing here
                        break;
                    case "recurring_payment":
                        #region Recurring payment
                        {
                            Guid orderNumberGuid = Guid.Empty;
                            try
                            {
                                orderNumberGuid = new Guid(rp_invoice_id);
                            }
                            catch
                            {
                            }

                            var initialOrder = _orderService.GetOrderByGuid(orderNumberGuid);
                            if (initialOrder != null)
                            {
                                var recurringPayments = _orderService.SearchRecurringPayments(0, initialOrder.Id, null);
                                foreach (var rp in recurringPayments)
                                {
                                    switch (newPaymentStatus)
                                    {
                                        case PaymentStatus.Authorized:
                                        case PaymentStatus.Paid:
                                            {
                                                var recurringPaymentHistory = rp.RecurringPaymentHistory;
                                                if (recurringPaymentHistory.Count == 0)
                                                {
                                                    //first payment
                                                    var rph = new RecurringPaymentHistory()
                                                    {
                                                        RecurringPaymentId = rp.Id,
                                                        OrderId = initialOrder.Id,
                                                        CreatedOnUtc = DateTime.UtcNow
                                                    };
                                                    rp.RecurringPaymentHistory.Add(rph);
                                                    _orderService.UpdateRecurringPayment(rp);
                                                }
                                                else
                                                {
                                                    //next payments
                                                    _orderProcessingService.ProcessNextRecurringPayment(rp);
                                                    //UNDONE change new order status according to newPaymentStatus
                                                    //UNDONE refund/void is not supported
                                                }
                                            }
                                            break;
                                    }
                                }

                                //this.OrderService.InsertOrderNote(newOrder.OrderId, sb.ToString(), DateTime.UtcNow);
                                _logger.Information("PayPal IPN. Recurring info", new NopException(sb.ToString()));
                            }
                            else
                            {
                                _logger.Error("PayPal IPN. Order is not found", new NopException(sb.ToString()));
                            }
                        }
                        #endregion
                        break;
                    default:
                        #region Standard payment
                        {
                            string orderNumber = string.Empty;
                            values.TryGetValue("custom", out orderNumber);
                            Guid orderNumberGuid = Guid.Empty;
                            try
                            {
                                orderNumberGuid = new Guid(orderNumber);
                            }
                            catch
                            {
                            }

                            var order = _orderService.GetOrderByGuid(orderNumberGuid);
                            if (order != null)
                            {

                                //order note
                                order.OrderNotes.Add(new OrderNote()
                                {
                                    Note = sb.ToString(),
                                    DisplayToCustomer = false,
                                    CreatedOnUtc = DateTime.UtcNow
                                });
                                _orderService.UpdateOrder(order);

                                switch (newPaymentStatus)
                                {
                                    case PaymentStatus.Pending:
                                        {
                                        }
                                        break;
                                    case PaymentStatus.Authorized:
                                        {
                                            if (_orderProcessingService.CanMarkOrderAsAuthorized(order))
                                            {
                                                _orderProcessingService.MarkAsAuthorized(order);
                                            }
                                        }
                                        break;
                                    case PaymentStatus.Paid:
                                        {
                                            if (_orderProcessingService.CanMarkOrderAsPaid(order))
                                            {
                                                _orderProcessingService.MarkOrderAsPaid(order);
                                            }
                                        }
                                        break;
                                    case PaymentStatus.Refunded:
                                        {
                                            if (_orderProcessingService.CanRefundOffline(order))
                                            {
                                                _orderProcessingService.RefundOffline(order);
                                            }
                                        }
                                        break;
                                    case PaymentStatus.Voided:
                                        {
                                            if (_orderProcessingService.CanVoidOffline(order))
                                            {
                                                _orderProcessingService.VoidOffline(order);
                                            }
                                        }
                                        break;
                                    default:
                                        break;
                                }
                            }
                            else
                            {
                                _logger.Error("PayPal IPN. Order is not found", new NopException(sb.ToString()));
                            }
                        }
                        #endregion
                        break;
                }
            }
            else
            {
                _logger.Error("PayPal IPN failed.", new NopException(strRequest));
            }

            //nothing should be rendered to visitor
            return Content("");
        }

        public ActionResult SubmitButton()
        {
            try
            {
                //user validation
                if ((_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed))
                    return RedirectToRoute("Login");

                var cart = _workContext.CurrentCustomer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList();

                if (cart.Count == 0)
                    return RedirectToRoute("ShoppingCart");

                //USD for US dollars, GBP for British pounds, SEK for Swedish krona, EUR for Euro etc
                var currency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode;

                if (String.IsNullOrEmpty(_paypalExpressPaymentSettings.ApiAccountName))
                    throw new ApplicationException("PayPal API Account Name is not set");
                if (String.IsNullOrEmpty(_paypalExpressPaymentSettings.ApiAccountPassword))
                    throw new ApplicationException("PayPal API Password is not set");
                if (String.IsNullOrEmpty(_paypalExpressPaymentSettings.Signature))
                    throw new ApplicationException("PayPal API Signature is not set");

                var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress") as PayPalExpressCheckoutPaymentProcessor;
                if (processor == null ||
                    !processor.IsPaymentMethodActive(_paymentSettings) || !processor.PluginDescriptor.Installed)
                    throw new NopException("PayPal Express Checkout module cannot be loaded");

                var processPaymentRequest = new PayPalProcessPaymentRequest();

                //Get sub-total and discounts that apply to sub-total
                decimal orderSubTotalDiscountAmountBase = decimal.Zero;
                Discount orderSubTotalAppliedDiscount = null;
                decimal subTotalWithoutDiscountBase = decimal.Zero;
                decimal subTotalWithDiscountBase = decimal.Zero;
                _orderTotalCalculationService.GetShoppingCartSubTotal(cart,
                    out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount,
                    out subTotalWithoutDiscountBase, out subTotalWithDiscountBase);

                //order total
                decimal resultTemp = decimal.Zero;
                resultTemp += subTotalWithDiscountBase;

                // get customer
                int customerId = Convert.ToInt32(_workContext.CurrentCustomer.Id.ToString());
                var customer = _customerService.GetCustomerById(customerId);

                //Get discounts that apply to Total
                Discount appliedDiscount = null;
                var discountAmount = _orderTotalCalculationService.GetOrderTotalDiscount(customer, resultTemp, out appliedDiscount);

                //if the current total is less than the discount amount, we only make the discount equal to the current total        
                if (resultTemp < discountAmount)
                    discountAmount = resultTemp;

                //reduce subtotal
                resultTemp -= discountAmount;

                if (resultTemp < decimal.Zero)
                    resultTemp = decimal.Zero;

                decimal tempDiscount = discountAmount + orderSubTotalDiscountAmountBase;

                resultTemp = _currencyService.ConvertFromPrimaryStoreCurrency(resultTemp, _workContext.WorkingCurrency);
                if (tempDiscount > decimal.Zero)
                {
                    tempDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(tempDiscount, _workContext.WorkingCurrency);
                }

                processPaymentRequest.PaymentMethodSystemName = "Payments.PayPalExpress";
                processPaymentRequest.OrderTotal = resultTemp;
                processPaymentRequest.Discount = tempDiscount;
                
                processPaymentRequest.IsRecurringPayment = false;
                _workContext.CurrentCustomer.SelectedPaymentMethodSystemName = "Payments.PayPalExpress";
                _customerService.UpdateCustomer(_workContext.CurrentCustomer);
                processPaymentRequest.CustomerId = _workContext.CurrentCustomer.Id;
                this.Session["OrderPaymentInfo"] = processPaymentRequest;

                var resp = processor.SetExpressCheckout(processPaymentRequest, cart);

                if (resp.Ack == AckCodeType.Success)
                {
                    processPaymentRequest.PaypalToken = resp.Token;
                    processPaymentRequest.OrderGuid = new Guid();
                    this.Session["OrderPaymentInfo"] = processPaymentRequest;
                    var result = new RedirectResult(String.Format(_paypalExpressPaymentSettings.UseSandbox ? "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&useraction=commit&token={0}" : "https://www.paypal.com/webscr?cmd=_express-checkout&useraction=commit&token={0}", resp.Token));
                    return result;
                }
                else
                {
                    var error = new StringBuilder("We apologize, but an error has occured.<br />");
                    foreach (var errormsg in resp.Errors)
                    {
                        error.AppendLine(String.Format("{0} | {1} | {2}", errormsg.ErrorCode, errormsg.ShortMessage, errormsg.LongMessage));
                    }
                    return Content(error.ToString());
                }
            }
            catch (Exception exc)
            {
                return Content("Error: " + exc);
            }
        }

        [ValidateInput(false)]
        public ActionResult ShippingOptions()
        {
            byte[] param = Request.BinaryRead(Request.ContentLength);
            string input = "";

            try
            {
                var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress") as PayPalExpressCheckoutPaymentProcessor;
                if (processor == null || !processor.IsPaymentMethodActive(_paymentSettings) || !processor.PluginDescriptor.Installed)
                    throw new NopException("PayPal Express Checkout module cannot be loaded");

                input = Encoding.ASCII.GetString(param);

                int customerId = Convert.ToInt32(Request.QueryString["CustomerID"]);
                var customer = _customerService.GetCustomerById(customerId);

                var result = processor.GetInstantUpdateShippingOptions(input, customer);

                Response.Write(result);
            }
            catch (Exception ex)
            {
                _logger.Error("Error determining Shipping Options. Input string: " + input, ex);
            }
            return Content("");
        }

        public ActionResult GetDetails(string token)
        {
            var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress") as PayPalExpressCheckoutPaymentProcessor;
            if (processor == null ||
                !processor.IsPaymentMethodActive(_paymentSettings) || !processor.PluginDescriptor.Installed)
                throw new NopException("PayPal Express Checkout module cannot be loaded");

            var resp = processor.GetExpressCheckoutDetails(token);

            if (resp.Ack == AckCodeType.Success)
            {
                var paymentInfo = this.Session["OrderPaymentInfo"] as ProcessPaymentRequest;
                paymentInfo = processor.SetCheckoutDetails(paymentInfo, resp.GetExpressCheckoutDetailsResponseDetails);
                this.Session["OrderPaymentInfo"] = paymentInfo;
                var customer = _customerService.GetCustomerById(paymentInfo.CustomerId);

                _workContext.CurrentCustomer = customer;
                _customerService.UpdateCustomer(_workContext.CurrentCustomer);

                var result = _orderProcessingService.PlaceOrder(paymentInfo);
                if (!result.Success)
                {
                    //LogMessage("new-order-notification received. CreateOrder() error: Order Number " + googleOrderNumber + ". " + result);
                    //return;
                }

                var order = result.PlacedOrder;
                if (order != null)
                {
                 
                }

                return RedirectToRoute("CheckoutCompleted");
            }
            
            return Content("somekind of error handling<br />");
        }
    }
}